#ifndef __tenacitas_async__handlers__h__
#define __tenacitas_async__handlers__h__


// =============================================================================
/**
   @file 
   File description
*/


// =============================================================================
//                 C++ Headers

#include <memory>
#include <list>

// =============================================================================
//                 3rds Headers

// =============================================================================
//                 Our Headers

#include "executer.h"
#include "unhandled.h"

#include <tenacitas_log/logger.h>

// =============================================================================
//                 Namespaces
using namespace std;

namespace ten_asy = tenacitas::async;

// =============================================================================
//                 Macro Contants

/** */


// =============================================================================
//                 Macro Commands

/** */


/** namespace of the organization */
namespace tenacitas {

    /** namespace for the project */
    namespace async {

        // =========================== Typedefs ===========================
        /** */


        // ============================ Pre-Declarations ==================


        // ============================ Attributes ========================
        /** */


        // ============================ Functions =========================
        /** */


        // ============================ Classes ===========================
        
        /** @brief A pool of handlers, handling a certain type of event 
            asynchronously

            ####################################################################
            <b>Template Parameters - summary</b>
            ####################################################################

            @tparam t_handler_factory is responsible for creating handler
            objects that will handle the events.

            @tparam t_unhandled_events manages the events that could not be 
            handled right away

            @tparam t_logger provides log methods. It should be a
            specialization of @p tenacitas::log::logger template class, or define
            the same behaviour.

            @tparam t_event_parms are the parameters of the event to be handled 

            ####################################################################
            <b>Template Parameters - detail</b>
            ####################################################################

            ********************************************************************
            <b>t_handler_factory</b>

            The @p t_handler_factory must implement:

            - <tt>typedef handler_t</tt>, which is the the class of the objects
            that will actually handle the events

            - <tt>typedef handler_ptr_t</tt>, which is the type of pointer to 
            @p handler_t

            - <tt>handler_ptr_t first_creation( )</tt>: this method is called
            repeatedly when a @p handlers object is created, until
            <tt>handler_ptr_t( )</tt> is returned. The purpose is to provide in
            advance a number of @p handler_ptr_t objects to handle events,
            avoiding to create them one at a time.

            - <tt>handler_ptr_t additional_creation( )</tt>: this method is
            called when all the existing @p handler_t are busy, handling
            events. Like @p first_creation, it is called in a loop until
            <tt>handler_ptr_t( )</tt>  is returned.

            <attention> In order to avoid an infinite loop, both @p
            first_creation and @p additional_creation must, eventualy, return
            <tt>handler_ptr_t( )</tt>  

            ********************************************************************
            <b>t_handler_factory::handler_t</b>

            The actual handler of the event, defined by @p t_handler_factory, must 
            implement:

            - <tt>void operator( ) ( t_1 &, t_2 &, ...  )</tt>, where @p t_1,
            @p t_2, and so on must match the same types defined in the template
            parameter @p t_event_parms

            ********************************************************************
            <b>t_unhandled_events</b>

            First, it is important to notice that @p handlers class define:
            
            - <tt>std::tuple<t_event_args...> event_args_tuple_t</tt> which is
            how the variadic type parameters will be packed 

            - <tt>std::shared_ptr<event_args_tuple_t> event_args_tuple_ptr_t, the
            type of pointer to @p event_args_tuple_t 

            Then, @p t_unhandled_events type must implement:

            - <tt>void add( event_args_tuple_ptr_t )</tt>, which adds a pointer
            to a packed event arguments to the list of unhandled events

            - <tt>event_args_tuple_ptr_t remove( )</tt>, which removes a pointer
            to a packed event arguments from the list of unhandled events

            - <tt>bool empty( ) const</tt>, which indicates if the list of
            unhandled events is empty

            -<tt>size_t size( ) const</tt>, which retrieves the number of
            unhandled events 


            <b>Please, take a look at <tt>first.h</tt>, and <tt>main.cpp</tt> for 
            an example.

            @nosubgrouping 
        */
        template <typename t_handler_factory,	  
                  typename t_unhandled_events,
                  typename t_logger,
                  typename... t_event_parms>
        class handlers {

        public:

            // --- handlers - Public Internal Types ------------------------

            /** Type of the pointer to the handlers */
            typedef std::shared_ptr<handlers> ptr_t;

            /** Type of the factory of the handlers */
            typedef t_handler_factory handler_factory_t;

            /** Type of pointer to the factory of event handlers */
            typedef typename std::shared_ptr<handler_factory_t>
            handler_factory_ptr_t; 

            /** Type of the handler that the factory creates */
            typedef typename handler_factory_t::handler_t handler_t;

            /** Type of the pointer to handler that the factory creates */
            typedef typename handler_factory_t::handler_ptr_t handler_ptr_t;

            /** Type of logger */
            typedef t_logger logger_t;

            /** Type of pointer to the logger */
            typedef typename logger_t::ptr_t logger_ptr_t;

            /** Type of the list of unhandled events */
            typedef t_unhandled_events unhandled_events_t;

            /** Type of pointer to the list of unhandled events */
            typedef typename std::shared_ptr<unhandled_events_t>
            unhandled_events_ptr_t; 

            /** Grouping the types of the arguments to the handler */
            typedef std::tuple<t_event_parms...> event_args_tuple_t;

            /** Type of pointer to the grouping the types of the arguments to the handler */
            typedef typename std::shared_ptr<event_args_tuple_t> event_args_tuple_ptr_t;



            // --- handlers - Public Constructors --------------------------

            /** Constructor

                @param [in] p_handler_factory will create the objects that will 
                actually handle the events 

                @param [in] p_unhandled_events will keep events that could not be 
                handled right away, and retrieve events when handlers are
                available
                
                @param [in] p_logger will be used to log messages
            */
            handlers( handler_factory_ptr_t p_handler_factory,
                      unhandled_events_ptr_t p_unhandled_events,
                      logger_ptr_t p_logger )
                : m_handler_factory( p_handler_factory ),
                  m_unhandled_events( p_unhandled_events ),
                  m_logger( p_logger ),
                  m_mutex_tasks( ),

                  m_busy( ),
                  m_free( ),

                  m_cond_all_free( ),
                  m_cond_new_free( ),

                  m_thread_unhandled( ),
                  m_thread_unhandled_on( false ),

                  m_thread_additional_handler( ),
                  m_thread_additional_handler_on( false ),

                  m_last_task_id( 0 ) {

                log_begin( m_logger );

                assert( m_handler_factory );

                assert( m_unhandled_events );

                assert( p_logger );
                

                // the handler_factory_t is responsible for creating the first
                // handler_ptr_t objects
                while ( true ) {
                    handler_ptr_t l_handler = m_handler_factory->first_creation( ); 
                    if ( !l_handler ) { break; }
                    add( l_handler );
                }

                log_end( m_logger );
            }

            /** Destructor */
            ~handlers ( ) {
        
                log_begin( m_logger );

                // waiting for all the tasks to finish
                wait( );

                // deleting all the @p tasks in the list of tasks
                for ( iterator l_ite = m_free.begin( );
                      l_ite != m_free.end( );
                      ++l_ite ) {
                    delete ( *l_ite );
                    *l_ite = 0;
                }

                log_end( m_logger );
            }


            // --- handlers - Public Processors ----------------------------

            /** Starts to handle an event with @p p_event_parms */
            void handle( t_event_parms... p_event_parms ) {
                handle( p_event_parms..., ++m_last_task_id );
            }

            /** Stops handling events */
            void stop( ) {
                log_begin( m_logger );
                wait( );
                log_end( m_logger );        
            }

            /** Waits for all the on-going handling to finish */
            void wait( ) {
        
                log_begin( m_logger );

                boost::mutex l_mutex;

                boost::unique_lock<boost::mutex> l_lock( l_mutex );
                if ( !m_busy.empty( ) ) {
                    // there are still busy tasks 
                    log_trace( m_logger ) << "waiting for all the handlers to be free... ";
                    // let's wait for all to be free
                    m_cond_all_free.wait( l_lock );
                }

                log_trace( m_logger ) << string( "all handlers are free" );

                // here we wait for the free tasks that may be finishing their work;
                // this is done because, by experience, the handling may finish,
                // but the task may still be running
                for ( iterator l_ite = m_free.begin( );
                      l_ite != m_free.end( );
                      ++l_ite ) {
                    ( *l_ite )->wait( );
                }

                // waiting for the thread that manages the unhandled events list
                // to finish 
                if ( m_thread_unhandled.get_id( ) != boost::thread::id( ) ) {
                    m_thread_unhandled.join( );
                }

                // waiting for the thread that tries to create a new handler to finish
                if ( m_thread_additional_handler.get_id( ) != boost::thread::id( ) ) {
                    m_thread_additional_handler.join( );
                }
                log_end( m_logger );
            }

        private:

            // --- handlers - Private Internal Types ----------------------------

            /** Type for the identifier of a task */
            typedef std::uint16_t task_id;

            /** A task puts a @p handler_ptr_t object in a thread, to handle an
                event asynchronously */
            class task {
            public:

                // --- task - Public Internal Types ----------------------------------

                /** Type for pointer of a task */
                typedef task * ptr_t;

	  
                // --- task - Public Constructors ----------------------------------

                /** Constructor

                    @param [in] p_handler is a pointer to the handler object
                    which <tt>operator( )</tt>  operator will be executed in a thread

                    @param [in] p_id is the identifier of this task
                */
                task( handler_ptr_t p_handler, task_id p_id, logger_ptr_t p_logger )
                    : m_mutex_exec( ),
                      m_handler ( p_handler ),
                      m_thread( ),
                      m_id( p_id ),
                      m_logger( p_logger ),
                      m_original ( boost::this_thread::get_id( ) ) {
                    log_begin( m_logger );
                    log_end( m_logger );
                }

                /** Destructor */
                ~task ( ) {
                    log_begin( m_logger );
                    // waiting for the thread to finish
                    wait( );
                    log_end( m_logger );
                }

                // --- task - Public Accessors ----------------------------------

                /** Retrieves the identifier of the task */
                const task_id & id( ) const {
                    log_begin( m_logger );
                    log_end( m_logger );
                    return m_id;
                }

	  
                // --- task - Public Processors ----------------------------------

                /** Waits for the thread in the task to finish */
                void wait( ) {
                    log_begin( m_logger );

                    if ( ( m_thread.get_id( ) != m_original ) &
                         ( m_thread.get_id( ) != boost::thread::id( ) ) ) {
                        m_thread.join( );
                    }
                    log_end( m_logger );

                }

                /** Stops the thread in the task

                    Actually it is the same as @p wait
                */
                void stop( ) {
                    log_begin( m_logger );
                    this->wait( );
                    log_end( m_logger );
                }

                /** Starts the task

                    @tparam t_task_pos is the type of the position this task
                    occupies in the pool of tasks in @p handlers object

                    @param [in] p_handlers is, well, the @p handlers object that
                    owns the pool of tasks

                    @param [in] p_position is the position this task occupies in
                    the pool of tasks in @p handlers object

                    @param [in] p_event_params are the parameters of the event
                    that shall be handled 
                */
                template <typename t_task_pos>
                void start( handlers * p_handlers,
                            t_task_pos p_position,
                            t_event_parms... p_event_parms ) {
  
                    log_begin( m_logger );

                    wait( );

                    // starting the thread... "go, forrest, go!!!"
                    m_thread = boost::thread ( &task::start_in_thread<t_task_pos>,
                                               this,
                                               p_handlers,
                                               p_position,
                                               p_event_parms... ); 

                    log_trace( m_logger ) << string( "thread " ) << m_id
                                          << string( " launched" ); 

                    log_end( m_logger );        
                }

            private:
	  
                // --- task - Private Processors ----------------------------------

                /** The method that will be executed in a separeted thread

                    @tparam t_task_pos is the type of the position this task
                    occupies in the pool of tasks in @p handlers object

                    @param [in] p_handlers is, well, the @p handlers object that
                    owns the pool of tasks

                    @param [in] p_position is the position this task occupies in
                    the pool of tasks in @p handlers object

                    @param [in] p_event_params are the parameters of the event
                    that shall be handled 
                */
                template <typename t_task_pos>
                void start_in_thread( handlers * p_handlers,
                                      t_task_pos p_position,
                                      t_event_parms... p_event_parms ) {


                    log_begin( m_logger );
                    {
                        boost::lock_guard<boost::mutex> l_lock( m_mutex_exec );

                        ( *m_handler )( p_event_parms... );

                    }
                    // the handler is set as available
                    p_handlers->set_available( p_position );

                    log_end( m_logger );
                }

	  
                // --- task - Private Attributes ----------------------------------

                /** Mutex to protect the execution of the task */
                boost::mutex m_mutex_exec;

                /** The handler */
                handler_ptr_t m_handler;

                /** The thread */
                boost::thread m_thread;

                /** Task id */
                task_id m_id;

                /** Logger */
                logger_ptr_t m_logger;

                /** This is used to determine if a previous thread is still
                    running */
                boost::thread::id m_original;
            };

            /** Type for the task */
            typedef task task_t;

            /** Type of pointer for the task */
            typedef typename task_t::ptr_t task_ptr_t;

            /** Type of the list of (pointer to) tasks */
            typedef  list<task_ptr_t> task_list;

            /** Type for the iterator in the list of (pointer to) tasks */
            typedef typename task_list::iterator iterator;


            // --- handlers - Private Constructors ---------------------------------
	
            /** It's forbiden to copy */
            handlers( const handlers & );

            /** It's forbiden to copy */
            handlers( const handlers && );

            // --- handlers - Private Accessors ------------------------------------

            /** Adds a @p handler_ptr_t into the list of handler objects

                @param [in] p_handler a handler object that will be added to the
                pool of handlers
            */
            void add( handler_ptr_t p_handler )  {
                boost::lock_guard<boost::mutex> l_lock(m_mutex_tasks);
                m_free.push_back( task_ptr_t( new task_t( p_handler,
                                                          ++m_last_task_id,
                                                          m_logger ) ) );
            }

            // --- handlers - Private Processors -----------------------------------

            /** Tries to find an available task_ptr_t

                @param [out] p_position is the position, if found, of an
                available task_ptr_t

                @return @p true if an available task_ptr_t was found, @p false
                otherwise 
            */
            bool find_available( iterator & p_position ) {

                log_begin( m_logger );

                boost::lock_guard<boost::mutex> l_lock( m_mutex_tasks );

                log_trace( m_logger ) << "trying to find an available handler";

                bool l_available = false;

                // "I dont think we'll make it", Chicken Little

                if ( !m_free.empty( ) ) {

                    log_trace( m_logger ) << "there is at least one free handler";

                    // there is at least one free handler
                    iterator l_free = m_free.begin( );

                    // that will become busy
                    m_busy.push_back( *l_free );

                    iterator l_busy = m_busy.end( );
                    --l_busy;

                    // removing the task from the free task list
                    m_free.erase( l_free );

                    log_trace( m_logger ) << "task id " << ( *l_busy )->id( )
                                          << " will become busy ";

                    log_trace( m_logger ) << "m_free.size = " << m_free.size( )
                                          << ", m_busy.size = " << m_busy.size( );

                    p_position = l_busy;

                    l_available = true;
                }

                log_end( m_logger );
                return ( l_available );
            }

            /** Indicates that a task_ptr_t is available

                @param [in] p_position is the position where a task_ptr_t is
                available */
            void set_available( iterator & p_position ) {

                log_begin( m_logger );

                log_trace( m_logger ) << "about to lock";

                boost::lock_guard<boost::mutex> l_lock(m_mutex_tasks);

                // acquiring the task_ptr_t in the position
                task_ptr_t l_task = *( p_position );

                log_trace( m_logger ) << "marking task " << l_task->id( )
                                      << " as available";
                // add the free handler to the free handlers list
                m_free.push_back( l_task );

                log_trace( m_logger ) << "removing it from the busy task list";

                // remove it from the busy handlers list
                m_busy.erase( p_position );

                log_trace( m_logger ) 
                    << "m_busy.empty( ) = " << std::boolalpha << m_busy.empty( )
                    << "m_unhandled_events.empty( ) = " << std::boolalpha
                    << m_unhandled_events->empty( );

                if ( ( m_busy.empty( ) ) & ( m_unhandled_events->empty( ) ) ) {
                    log_trace( m_logger ) << "m_busy.empty and m_unhandled_events.empty";
                    // all the handlers are free, and there is no unhandled
                    // event, so we sign that all the handlers are free
                    m_cond_all_free.notify_all( );
                }

                log_trace( m_logger ) << "about to notify all";

                // and here we sign that there is a new available handler 
                m_cond_new_free.notify_all( );

                log_trace( m_logger ) << "all notified";

                log_end( m_logger );
            }

            /** Manages the list of unhandled events */
            void unhandled( ) {

                m_thread_unhandled_on = true;

                log_begin( m_logger );

                while ( true ) {
                    if ( m_unhandled_events->empty( ) ) {
                        // no unhandled event waiting to be handled
                        log_trace( m_logger ) << "no unhandled events";
                        break;
                    }
                    else {

                        if ( m_free.empty( ) ) {
                            // there are events waiting to be handled, but no
                            // free handler, so we wait for someone to sign that
                            // there is an available handler

                            log_trace( m_logger )
                                << "waiting for a handler to be free";

                            boost::mutex l_mutex_new_free;

                            boost::unique_lock<boost::mutex>
                                l_lock_new_free( l_mutex_new_free );

                            m_cond_new_free.wait( l_lock_new_free );
                        }

                        // someone signed that a handler became available

                        log_trace( m_logger )
                            << "passing an unhandled event to a handler";

                        // we remove an event from the unhandled events list
                        event_args_tuple_ptr_t l_event_parms_tuple =
                            m_unhandled_events->remove( );

                        // and put it to be handled again

                        typedef typename std::tuple_size<event_args_tuple_t> event_args_tuple_size_t;

                        typedef executer<
                            handlers<t_handler_factory,
                            t_unhandled_events,
                            t_logger,
                            t_event_parms...>,
                            event_args_tuple_t,
                            event_args_tuple_size_t::value> executer;

                        log_trace( m_logger ) << string( "about to execute" );
                        executer( )( this, *l_event_parms_tuple );
                        log_trace( m_logger ) << string( "execution finished" );

                    }
                }

                m_thread_unhandled_on = false;

                log_end( m_logger );
            }

            /** Tries to create a new handler, when none is available */
            void additional_handler( ) {

                m_thread_additional_handler_on = true;

                log_begin( m_logger );

                uint16_t l_i = 0;
                while ( true ) {
                    handler_ptr_t l_handler =
                        m_handler_factory->additional_creation( );  
                    if ( !l_handler ) { 
                        log_trace( m_logger ) << "no more handlers to be added";
                        break; 
                    }
                    log_trace( m_logger ) << "adding new handler # " << ++l_i;
                    add( l_handler );
                }

                m_thread_additional_handler_on = false;

                log_end( m_logger );
            }

            /** Handles an @p event_ptr_t object, informing the identifier of the
                task created to allow the event to be handled asynchronously

                @param [in] p_event is an instance of event_t
                @param [out] p_id is the identifier of the task

                @return @p true if @p p_event was handled, @p false othewise
            */
            bool handle( t_event_parms... p_event_parms, task_id & p_id  ) {
        
                log_begin( m_logger );

                bool l_rc = false;

                iterator l_task_position;

                if ( find_available( l_task_position ) ) {
                    // a handler_ptr_t was found available

                    l_rc = true;

                    // retrieveing the id of the task
                    p_id = ( *l_task_position )->id( );

                    // asking the task to handle the event
                    ( *l_task_position )->start( this, l_task_position,
                                                 p_event_parms... ); 
                }
                else  {
                    // no handler_ptr_t was found available

                    log_warn( m_logger ) << "no handler available";

                    // trying to create a new handler
                    if ( !m_thread_additional_handler_on ) {
                        log_trace( m_logger )
                            << "starting handler that will try to " 
                            << "create new handlers";

                        // why to do this? well, by experience, the function
                        // carried on a thread may finish, but the thread id does
                        // not change to boost::thread::id( ) right away
                        if ( m_thread_additional_handler.get_id( ) !=
                             boost::thread::id( ) ) { 
                            m_thread_additional_handler.join( );
                        }

                        m_thread_additional_handler =
                            boost::thread( &handlers::additional_handler, this );
                    }

                    // inserting a new event in the unhandled events list
                    event_args_tuple_ptr_t
                        l_event_parms_tuple( new event_args_tuple_t(
                                                 p_event_parms...) ) ; 
        
                    m_unhandled_events->add( l_event_parms_tuple );
                    log_warn( m_logger ) << "m_unhandled_events.size = " 
                                         << m_unhandled_events->size( );

                    // and firing the thread that will consume the list of
                    // unhandled events 
                    if ( !m_thread_unhandled_on  ) {

                        // why to do this? well, by experience, the function
                        // carried on a thread may finish, but the thread id does 
                        // not change to boost::thread::id( ) right away
                        if ( m_thread_unhandled.get_id( ) !=
                             boost::thread::id( ) ) { 
                            m_thread_unhandled.join( );
                        }

                        // starting the task that will manage the unhandled
                        // events list, removing an event to be handled, as a
                        // handler_ptr_t becomes available
                        log_trace( m_logger ) << "starting 'unhandled' thread";
                        m_thread_unhandled = boost::thread( &handlers::unhandled,
                                                            this ); 
                    }
                }

                log_end( m_logger );
                return l_rc;
            }


            // --- handlers - Private Operators ------------------------------------

            /** No assignments allowed */
            handlers & operator = ( const handlers & );

            // --- handlers - Private Attributes -----------------------------------

            /** Handler creator */
            handler_factory_ptr_t m_handler_factory;

            /** Manages unhandled events */
            unhandled_events_ptr_t m_unhandled_events;

            /** Logger */
            logger_ptr_t m_logger;

            /** Protects access to the tasks */
            boost::mutex m_mutex_tasks;

            /** List of bus tasks */
            task_list m_busy;

            /** List of free tasks */
            task_list m_free;

            /** Used to sign that all the tasks are free */
            boost::condition_variable m_cond_all_free;

            /** Used to sign that there is a new free task */
            boost::condition_variable m_cond_new_free;

            /** Thread to handle the list of unhandled events */
            boost::thread m_thread_unhandled;

            /** Indicates if the fuction associated with @p m_thread_unhandled is 
                still running */
            bool m_thread_unhandled_on;

            /** Thread that will try to create a new handler, when none was
                available */
            boost::thread m_thread_additional_handler;

            /** Indicates if the fuction associated with 
                @p m_thread_additional_handler is still running */
            bool m_thread_additional_handler_on;

            /** The last task id used */
            task_id m_last_task_id;
        };

    }
}

// =============================================================================
//           I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::async;




#endif
